﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace Logger
{
    /// <summary>
    /// A generic logger that can work with any third party logging solution
    /// </summary>
    /// <remarks>
    /// To use this logger with your 3rd party logging solution you just need to create a small adapter class. 
    /// Implement the Log delegate and hook it up to the OnLog event.  This event will be triggered for every log message, 
    /// allowing you take the log message and pass it on to your other logging solution.
    /// 
    /// For an example, see the ConsoleLogger class
    /// </remarks>
    public class Log
    {
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(_loggerName != null);
        }

        #region Instance
        /// <summary>
        /// Creates a new logger with the specified name
        /// </summary>
        /// <param name="name">The name of the logger</param>
        private Log(String name)
        {
            Contract.Requires(name != null);
            _loggerName = name;
        }

        /// <summary>
        /// Creates a new logger with the specified name
        /// </summary>
        /// <param name="name">The name of the logger</param>
        /// <returns>A logger object</returns>
        public static Log Create(String name)
        {
            Contract.Requires(name != null);
            Contract.Ensures(Contract.Result<Log>() != null);
            return new Log(name);
        }
        /// <summary>
        /// Creates a new logger with the name of the logger being the name of the type
        /// </summary>
        /// <param name="type">the type to use as the loggers name</param>
        /// <returns>A Logger object</returns>
        public static Log Create(Type type)
        {
            Contract.Requires(type != null);
            Contract.Ensures(Contract.Result<Log>() != null);
            return new Log(type.Name);
        }
        /// <summary>
        /// Creates a new logger with the name of the logger being the name of the type
        /// </summary>
        /// <typeparam name="T">the type to use as the loggers name</typeparam>
        /// <returns>A Logger object</returns>
        public static Log Create<T>()
        {
            Contract.Ensures(Contract.Result<Log>() != null);
            return Create(typeof(T));
        }

        /// <summary>
        /// The name of this logger
        /// </summary>
        private String _loggerName;

        /// <summary>
        /// Logs an error message with an accompaniying exception object
        /// </summary>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Error(Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Error(_loggerName, cause, message, args);
        }

        /// <summary>
        /// Logs a warning message with an accompaniying exception object
        /// </summary>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Warn(Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Warn(_loggerName, cause, message, args);
        }

        /// <summary>
        /// Logs an info message with an accompaniying exception object
        /// </summary>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Info(Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Info(_loggerName, cause, message, args);
        }

        /// <summary>
        /// Logs a debug message with an accompaniying exception object
        /// </summary>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Debug(Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Debug(_loggerName, cause, message, args);
        }

        /// <summary>
        /// Logs an error message
        /// </summary>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Error(String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Error(_loggerName, null, message, args);
        }

        /// <summary>
        /// Logs a warning message 
        /// </summary>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Warn(String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Warn(_loggerName, null, message, args);
        }

        /// <summary>
        /// Logs an info message
        /// </summary>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Info(String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Info(_loggerName, null, message, args);
        }

        /// <summary>
        /// Logs a debug message
        /// </summary>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public void Debug(String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Log.Debug(_loggerName, null, message, args);
        }
        #endregion
        #region Static

        /// <summary>
        /// The delegate to implement to receive log events from this Logger
        /// </summary>
        /// <param name="loggerName">The name of the logger</param>
        /// <param name="logLevel">The severity of the log message</param>
        /// <param name="cause">The exception associated with the message (optional)</param>
        /// <param name="message">The message to be logged</param>
        public delegate void LogDelegate(String loggerName, LogLevel logLevel, Exception cause, String message);
        
        /// <summary>
        /// An event that is triggered when there are messages to log
        /// </summary>
        public static event LogDelegate OnLog;

        /// <summary>
        /// Logs a custom message without using one of the builder methods or logger objects
        /// </summary>
        /// <param name="loggerName">The name of the logger</param>
        /// <param name="logLevel">The severity of the log message</param>
        /// <param name="cause">The exception associated with the message (optional)</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>       
        private static void LogCustom(String loggerName, LogLevel logLevel, Exception cause, String message, params Object[] args)
        {
            Contract.Requires(loggerName != null);
            Contract.Requires(message != null);
            if (OnLog != null)
            {
                try
                {
                    String msg = message;
                    if (args != null && args.Length != 0)
                    {
                        msg = String.Format(msg, args);
                    }
                    OnLog(loggerName, logLevel, cause, msg);
                }
                catch { }
            }
        }

        /// <summary>
        /// Logs an error message with an accompaniying exception object
        /// </summary>
        /// <param name="loggerName">The name of the class/module that generated the log message</param>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public static void Error(String loggerName, Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Contract.Requires(loggerName != null);
            LogCustom(loggerName, LogLevel.Error, cause, message, args);
        }

        /// <summary>
        /// Logs a warning message with an accompaniying exception object
        /// </summary>
        /// <param name="loggerName">The name of the class/module that generated the log message</param>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public static void Warn(String loggerName, Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Contract.Requires(loggerName != null);
            LogCustom(loggerName, LogLevel.Warn, cause, message, args);
        }

        /// <summary>
        /// Logs an info message with an accompaniying exception object
        /// </summary>
        /// <param name="loggerName">The name of the class/module that generated the log message</param>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public static void Info(String loggerName, Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Contract.Requires(loggerName != null);
            LogCustom(loggerName, LogLevel.Info, cause, message, args);
        }

        /// <summary>
        /// Logs a debug message with an accompaniying exception object
        /// </summary>
        /// <param name="loggerName">The name of the class/module that generated the log message</param>
        /// <param name="cause">The exception that caused the error</param>
        /// <param name="message">The message to be logged</param>
        /// <param name="args">Additional parameters to be inserted into the message as per String.Format</param>
        public static void Debug(String loggerName, Exception cause, String message, params Object[] args)
        {
            Contract.Requires(message != null);
            Contract.Requires(loggerName != null);
            LogCustom(loggerName, LogLevel.Debug, cause, message, args);
        }
        #endregion
    }

    #region LogLevel
    /// <summary>
    /// The possible severity levels for logged messages
    /// </summary>
    public enum LogLevel
    {
        /// <summary>
        /// A message which indicates a recoverable problem has occured
        /// </summary>
        Warn, 
        /// <summary>
        /// A message which indicates a non-recoverable problem has occured
        /// </summary>
        Error, 
        /// <summary>
        /// An information message that usually indicates normal activity
        /// </summary>
        Info, 
        /// <summary>
        /// An information message with additional details to help troubleshooting
        /// </summary>
        Debug
    }
    #endregion
}
